package main

import (
	"bufio"
	"embed"
	"flag"
	"fmt"
	"io"
	"math/rand"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/gdamore/tcell/v2"
	"github.com/martinlindhe/notify"
	"github.com/mitchellh/go-homedir"
	"github.com/pelletier/go-toml/v2"
)

//go:embed chillhop.txt
var embedFS embed.FS

type Opts struct {
	HighlightColor      string `toml:"highlight_color"`
	TextColor           string `toml:"text_color"`
	BorderColor         string `toml:"border_color"`
	FFplayPath          string `toml:"ffplay_path"`
	CachePath           string `toml:"cache_path"`
	Cursor              string `toml:"cursor"`
	LogLevel            string `toml:"log_level"`
	AutoPlay            bool   `toml:"auto_play"`
	CleanCacheAfterPlay bool   `toml:"clean_cache_after_play"`
	AutoPlayNext        bool   `toml:"auto_play_next"` // 新增：播放完自动续播下一首
	Help                bool
	Version             bool
}

type Track struct {
	ID   string
	Name string
}

var (
	opts           Opts
	tracks         []Track
	filteredTracks []Track
	selectedIdx    int
	query          string
	screen         tcell.Screen
	logFile        *os.File
	cacheDir       string
	ffplayCmd      *exec.Cmd
	currentTrack   *Track
	isPlaying      bool
	isPaused       bool
	isSearching    bool
	showHelp       bool
	showLog        bool
	helpScrollOff  int
	logScrollOff   int
	lastKey        rune
	mu             sync.Mutex
	random         *rand.Rand

	streamBaseURL = "https://stream.chillhop.com/mp3/"
	defaultConfig = `
highlight_color = "#5294e2"
text_color = "#ffffff"
border_color = "#3a3a3a"
ffplay_path = "ffplay"
cache_path = "~/.cache/chillhop-player"
cursor = "█"
log_level = "error"
auto_play = true
clean_cache_after_play = false
auto_play_next = true  # 播放完自动续播下一首（true/false）
`
)

func main() {
	random = rand.New(rand.NewSource(time.Now().UnixNano()))

	if err := initLogging(); err != nil {
		fmt.Fprintf(os.Stderr, "日志初始化失败: %v\n", err)
		os.Exit(1)
	}
	defer logFile.Close()

	logInfo("Chillhop播放器启动")

	if err := realMain(); err != nil {
		logError("程序错误: %v", err)
		fmt.Fprintf(os.Stderr, "错误: %v\n", err)
		fmt.Println("按回车键退出...")
		var input string
		fmt.Scanln(&input)
		os.Exit(1)
	}
}

func realMain() error {
	parseCLI()
	if opts.Help {
		printCliHelp()
		return nil
	}
	if opts.Version {
		fmt.Println("chillhop-player 0.5.0")
		return nil
	}

	configPath, err := getConfigPath()
	if err != nil {
		return fmt.Errorf("获取配置路径失败: %w", err)
	}
	if err := ensureConfig(configPath); err != nil {
		return fmt.Errorf("创建配置文件失败: %w", err)
	}
	if err := loadConfig(configPath); err != nil {
		return fmt.Errorf("加载配置失败: %w", err)
	}

	if err := initCacheDir(); err != nil {
		return fmt.Errorf("初始化缓存目录失败: %w", err)
	}

	if err := loadTracks(); err != nil {
		return fmt.Errorf("加载曲目失败: %w", err)
	}

	if err := initScreen(); err != nil {
		return fmt.Errorf("初始化终端失败: %w", err)
	}
	defer screen.Fini()

	if err := checkDependencies(); err != nil {
		return fmt.Errorf("依赖检查失败: %w", err)
	}

	if opts.AutoPlay && len(tracks) > 0 {
		drawStatus("启动自动播放...")
		go skipTrack()
	}

	return runUILoop()
}

func parseCLI() {
	flag.BoolVar(&opts.Help, "help", false, "显示帮助")
	flag.BoolVar(&opts.Help, "h", false, "显示帮助(短选项)")
	flag.BoolVar(&opts.Version, "version", false, "显示版本")
	flag.BoolVar(&opts.Version, "V", false, "显示版本(短选项)")
	flag.Parse()
}

func printCliHelp() {
	fmt.Println(`chillhop-player 0.5.0
用法: chillhop-player [选项]

功能: 搜索并播放chillhop曲目

选项:
  -h, --help      显示命令行帮助
  -V, --version   显示版本信息

UI快捷键:
  /           激活搜索框
  ?           显示帮助
  l           查看日志
  p           暂停/继续播放
  s           跳过当前曲目
  j/k         上下选择(Vim风格)
  gg          跳转到列表顶部(Vim风格)
  G           跳转到列表底部(Vim风格)
  上下键      选择曲目
  回车键      播放选中曲目
  Esc/Ctrl+C  退出程序
`)
}

func getConfigPath() (string, error) {
	home, err := homedir.Dir()
	if err != nil {
		return "", err
	}
	return filepath.Join(home, ".config", "chillhop-player", "config.toml"), nil
}

func ensureConfig(path string) error {
	if _, err := os.Stat(path); os.IsNotExist(err) {
		if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
			return err
		}
		if err := os.WriteFile(path, []byte(defaultConfig), 0644); err != nil {
			return err
		}
		logInfo("创建默认配置文件: %s", path)
	}
	return nil
}

func loadConfig(path string) error {
	content, err := os.ReadFile(path)
	if err != nil {
		return err
	}
	if err := toml.Unmarshal(content, &opts); err != nil {
		return err
	}

	if opts.CachePath != "" {
		expanded, err := homedir.Expand(opts.CachePath)
		if err != nil {
			logWarn("缓存路径解析失败，使用默认值: %v", err)
		} else {
			cacheDir = expanded
		}
	}
	logInfo("加载配置文件: %s", path)
	return nil
}

func initCacheDir() error {
	defaultCache, err := homedir.Expand("~/.cache/chillhop-player")
	if err != nil {
		return err
	}
	if cacheDir == "" {
		cacheDir = defaultCache
	}

	if err := os.MkdirAll(cacheDir, 0755); err != nil {
		return err
	}
	logInfo("缓存目录: %s", cacheDir)
	return nil
}

func removeCacheDir() {
	mu.Lock()
	defer mu.Unlock()

	if cacheDir == "" {
		return
	}
	if err := os.RemoveAll(cacheDir); err != nil {
		logError("删除缓存目录失败: %v", err)
		sendNotification("缓存清理失败", "退出时清理缓存失败")
	} else {
		logInfo("已删除缓存目录: %s", cacheDir)
		sendNotification("缓存清理完成", "退出时已清理所有缓存文件")
	}
}

func checkDependencies() error {
	if _, err := exec.LookPath(opts.FFplayPath); err != nil {
		return fmt.Errorf("未找到ffplay（需安装ffmpeg），请检查配置中的ffplay_path")
	}

	if _, err := embedFS.Open("chillhop.txt"); err != nil {
		return fmt.Errorf("未找到chillhop.txt（需放在程序同级目录，格式：ID!名称）")
	}

	logInfo("所有依赖检查通过")
	return nil
}

func initLogging() error {
	home, err := homedir.Dir()
	if err != nil {
		return err
	}
	logDir := filepath.Join(home, ".config", "chillhop-player", "logs")
	if err := os.MkdirAll(logDir, 0755); err != nil {
		return err
	}

	logPath := filepath.Join(logDir, fmt.Sprintf("chillhop-player-%s.log", time.Now().Format("20060102")))
	logFile, err = os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
	if err != nil {
		return err
	}
	return nil
}

func logInfo(format string, v ...interface{}) {
	if opts.LogLevel == "debug" || opts.LogLevel == "info" {
		msg := fmt.Sprintf("[INFO] %s: %s\n", time.Now().Format(time.RFC3339), fmt.Sprintf(format, v...))
		io.WriteString(logFile, msg)
	}
}

func logWarn(format string, v ...interface{}) {
	msg := fmt.Sprintf("[WARN] %s: %s\n", time.Now().Format(time.RFC3339), fmt.Sprintf(format, v...))
	io.WriteString(logFile, msg)
}

func logError(format string, v ...interface{}) {
	msg := fmt.Sprintf("[ERROR] %s: %s\n", time.Now().Format(time.RFC3339), fmt.Sprintf(format, v...))
	io.WriteString(logFile, msg)
}

func logDebug(format string, v ...interface{}) {
	if opts.LogLevel == "debug" {
		msg := fmt.Sprintf("[DEBUG] %s: %s\n", time.Now().Format(time.RFC3339), fmt.Sprintf(format, v...))
		io.WriteString(logFile, msg)
	}
}

func readRecentLogs() []string {
	home, err := homedir.Dir()
	if err != nil {
		return []string{"日志目录获取失败: " + err.Error()}
	}
	logPath := filepath.Join(home, ".config", "chillhop-player", "logs",
		fmt.Sprintf("chillhop-player-%s.log", time.Now().Format("20060102")))

	file, err := os.Open(logPath)
	if err != nil {
		return []string{"日志文件打开失败: " + err.Error()}
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	var lines []string
	for scanner.Scan() {
		lines = append(lines, scanner.Text())
	}
	if err := scanner.Err(); err != nil {
		return []string{"日志读取失败: " + err.Error()}
	}
	return lines
}

func loadTracks() error {
	file, err := embedFS.Open("chillhop.txt")
	if err != nil {
		return err
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	trackCount := 0
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" || !strings.Contains(line, "!") {
			continue
		}
		parts := strings.SplitN(line, "!", 2)
		if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
			logWarn("无效曲目格式: %s（已跳过）", line)
			continue
		}
		tracks = append(tracks, Track{ID: parts[0], Name: parts[1]})
		trackCount++
	}

	if err := scanner.Err(); err != nil {
		return err
	}
	if trackCount == 0 {
		return fmt.Errorf("chillhop.txt中无有效曲目（格式：ID!名称）")
	}

	filteredTracks = tracks
	selectedIdx = 0
	logInfo("加载曲目: %d首", trackCount)
	return nil
}

func filterTracks(query string) []Track {
	if query == "" {
		return tracks
	}

	queryLower := strings.ToLower(query)
	var filtered []Track
	for _, track := range tracks {
		if strings.Contains(strings.ToLower(track.Name), queryLower) {
			filtered = append(filtered, track)
		}
	}
	return filtered
}

func getTrackPath(track *Track) string {
	return filepath.Join(cacheDir, fmt.Sprintf("%s.mp3", track.ID))
}

func downloadTrack(track *Track) error {
	msg := "正在下载: " + track.Name
	drawStatusJustMsg(msg)
	sendNotification("开始下载", msg)

	trackPath := getTrackPath(track)
	if fileExists(trackPath) {
		msg := "曲目已缓存: " + track.Name
		drawStatusJustMsg(msg)
		sendNotification("下载完成", msg)
		logInfo(msg)
		return nil
	}

	logInfo("下载曲目: %s (ID: %s)", track.Name, track.ID)
	resp, err := http.Get(fmt.Sprintf("%s%s", streamBaseURL, track.ID))
	if err != nil {
		msg := "下载失败: 网络错误 - " + err.Error()
		drawStatusJustMsg(msg)
		sendNotification("下载失败", msg)
		return fmt.Errorf(msg)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		msg := fmt.Sprintf("下载失败: HTTP状态码 %d", resp.StatusCode)
		drawStatusJustMsg(msg)
		sendNotification("下载失败", msg)
		return fmt.Errorf(msg)
	}

	file, err := os.Create(trackPath)
	if err != nil {
		msg := "下载失败: 创建文件失败 - " + err.Error()
		drawStatusJustMsg(msg)
		sendNotification("下载失败", msg)
		return fmt.Errorf(msg)
	}
	defer file.Close()

	_, err = io.Copy(file, resp.Body)
	if err != nil {
		os.Remove(trackPath)
		msg := "下载失败: 写入文件失败 - " + err.Error()
		drawStatusJustMsg(msg)
		sendNotification("下载失败", msg)
		return fmt.Errorf(msg)
	}

	msg = "下载完成: " + track.Name
	drawStatusJustMsg(msg)
	sendNotification("下载完成", msg)
	logInfo(msg)
	return nil
}

func playTrack(track *Track) error {
	if track == nil {
		msg := "播放失败: 无效曲目"
		drawStatusJustMsg(msg)
		return fmt.Errorf(msg)
	}

	drawStatus("准备播放: " + track.Name)
	if err := downloadTrack(track); err != nil {
		drawStatus("播放失败: " + err.Error())
		return err
	}

	stopPlayback()

	mu.Lock()
	currentTrack = track
	isPlaying = true
	isPaused = false
	mu.Unlock()

	trackPath := getTrackPath(track)
	cmd := exec.Command(opts.FFplayPath, "-nodisp", "-autoexit", trackPath)
	mu.Lock()
	ffplayCmd = cmd
	mu.Unlock()

	logInfo("开始播放: %s", track.Name)
	drawStatus("正在播放: " + track.Name)
	sendNotification("播放开始", "正在播放: "+track.Name)

	if err := cmd.Start(); err != nil {
		mu.Lock()
		isPlaying = false
		mu.Unlock()
		msg := "播放启动失败: " + err.Error()
		drawStatusJustMsg(msg)
		sendNotification("播放失败", msg)
		return fmt.Errorf(msg)
	}

	go func(t Track) {
		cmd.Wait()
		mu.Lock()
		defer mu.Unlock()

		if !isPaused {
			isPlaying = false
			if opts.CleanCacheAfterPlay {
				if err := os.Remove(getTrackPath(&t)); err != nil {
					logWarn("清理单首曲目缓存失败: %v", err)
				} else {
					logInfo("已清理缓存: %s", t.Name)
				}
			}
			currentTrack = nil
			logInfo("播放结束: %s", t.Name)
			drawStatus("播放结束: " + t.Name)
			sendNotification("播放结束", "曲目播放完成: "+t.Name)

			// 新增：自动续播下一首
			if opts.AutoPlayNext && len(tracks) > 0 {
				drawStatus("正在播放下一首...")
				go skipTrack()
			}
		}
	}(*track)

	return nil
}

func togglePause() string {
	mu.Lock()
	defer mu.Unlock()

	if !isPlaying || ffplayCmd == nil || ffplayCmd.Process == nil {
		msg := "无播放曲目"
		drawStatusJustMsg(msg)
		return msg
	}

	if isPaused {
		if runtime.GOOS == "windows" {
			if currentTrack != nil {
				go playTrack(currentTrack)
			}
		} else {
			ffplayCmd.Process.Signal(syscall.SIGCONT)
		}
		isPaused = false
		msg := "继续播放: " + currentTrack.Name
		drawStatusJustMsg(msg)
		sendNotification("播放继续", msg)
		return msg
	} else {
		if runtime.GOOS == "windows" {
			ffplayCmd.Process.Kill()
		} else {
			ffplayCmd.Process.Signal(syscall.SIGSTOP)
		}
		isPaused = true
		msg := "已暂停: " + currentTrack.Name
		drawStatusJustMsg(msg)
		sendNotification("播放暂停", msg)
		return msg
	}
}

func skipTrack() string {
	mu.Lock()
	current := currentTrack
	mu.Unlock()

	stopPlayback()
	drawStatus("正在寻找下一首...")

	if len(tracks) == 0 {
		msg := "无曲目可播放"
		drawStatusJustMsg(msg)
		return msg
	}

	var candidate Track
	if len(tracks) == 1 {
		candidate = tracks[0]
	} else {
		for {
			idx := random.Intn(len(tracks))
			candidate = tracks[idx]
			if current == nil || candidate.ID != current.ID {
				break
			}
		}
	}

	go func(t Track) {
		if err := playTrack(&t); err != nil {
			logError("随机播放失败: %v", err)
			drawStatus("播放失败: " + err.Error())
		}
	}(candidate)

	msg := candidate.Name
	drawStatusJustMsg(msg)
	sendNotification("曲目跳过", msg)
	return msg
}

func stopPlayback() {
	mu.Lock()
	defer mu.Unlock()

	if ffplayCmd != nil && ffplayCmd.Process != nil {
		ffplayCmd.Process.Kill()
	}
	ffplayCmd = nil
	isPlaying = false
	isPaused = false
}

func fileExists(path string) bool {
	_, err := os.Stat(path)
	return !os.IsNotExist(err)
}

func sendNotification(title, message string) {
	go func() {
		notify.Notify("Chillhop Player", title, message, "")
	}()
}

func initScreen() error {
	var err error
	screen, err = tcell.NewScreen()
	if err != nil {
		return err
	}
	if err := screen.Init(); err != nil {
		return err
	}

	textColor := getColor(opts.TextColor)
	screen.SetStyle(tcell.StyleDefault.
		Foreground(textColor).
		Background(tcell.ColorBlack))
	screen.Clear()
	return nil
}

func runUILoop() error {
	for {
		drawUI()

		ev := screen.PollEvent()
		switch ev := ev.(type) {
		case *tcell.EventKey:
			if handleKeyEvent(ev) {
				return nil
			}
		case *tcell.EventResize:
			screen.Sync()
		}
	}
}

func handleKeyEvent(ev *tcell.EventKey) bool {
	defer func() {
		if ev.Rune() == 'g' {
			lastKey = 'g'
		} else {
			lastKey = 0
		}
	}()

	if showHelp || showLog {
		maxScroll := 0
		currentScroll := 0
		_, h := screen.Size()
		boxH := h * 4 / 5

		if showHelp {
			contentLen := len(getHelpContent())
			maxScroll = max(0, contentLen-(boxH-4))
			currentScroll = helpScrollOff
		} else {
			contentLen := len(getLogContent())
			maxScroll = max(0, contentLen-(boxH-4))
			currentScroll = logScrollOff
		}

		switch {
		case ev.Rune() == 'g' && lastKey == 'g':
			if showHelp {
				helpScrollOff = 0
			} else {
				logScrollOff = 0
			}
			return false
		case ev.Rune() == 'G':
			if showHelp {
				helpScrollOff = maxScroll
			} else {
				logScrollOff = maxScroll
			}
			return false
		case ev.Key() == tcell.KeyUp, ev.Rune() == 'k':
			if currentScroll > 0 {
				if showHelp {
					helpScrollOff--
				} else {
					logScrollOff--
				}
			}
			return false
		case ev.Key() == tcell.KeyDown, ev.Rune() == 'j':
			if currentScroll < maxScroll {
				if showHelp {
					helpScrollOff++
				} else {
					logScrollOff++
				}
			}
			return false
		default:
			if showHelp {
				showHelp = false
				helpScrollOff = 0
			} else {
				showLog = false
				logScrollOff = 0
			}
			return false
		}
	}

	if isSearching {
		switch {
		case ev.Key() == tcell.KeyEsc:
			isSearching = false
			query = ""
			filteredTracks = tracks
			selectedIdx = 0
		case ev.Key() == tcell.KeyEnter:
			// isPaused = true
			if len(filteredTracks) == 0 {
				drawStatus("无匹配曲目")
				return false
			}
			selectedIdx = clamp(selectedIdx, 0, len(filteredTracks)-1)
			go func(t Track) {
				if err := playTrack(&t); err != nil {
					logError("播放失败: %v", err)
					drawStatus("播放失败: " + err.Error())
				}
			}(filteredTracks[selectedIdx])
		case ev.Key() == tcell.KeyBackspace, ev.Key() == tcell.KeyBackspace2:
			if len(query) > 0 {
				query = query[:len(query)-1]
				filteredTracks = filterTracks(query)
				selectedIdx = clamp(selectedIdx, 0, len(filteredTracks)-1)
			}
		case ev.Key() == tcell.KeyDown, ev.Rune() == 'j':
			if len(filteredTracks) > 0 {
				selectedIdx = (selectedIdx + 1) % len(filteredTracks)
			}
		case ev.Key() == tcell.KeyUp, ev.Rune() == 'k':
			if len(filteredTracks) > 0 {
				selectedIdx = (selectedIdx - 1 + len(filteredTracks)) % len(filteredTracks)
			}
		case ev.Rune() == 'g' && lastKey == 'g':
			selectedIdx = 0
		case ev.Rune() == 'G':
			if len(filteredTracks) > 0 {
				selectedIdx = len(filteredTracks) - 1
			}
		default:
			if ev.Rune() != 0 {
				query += string(ev.Rune())
				filteredTracks = filterTracks(query)
				selectedIdx = 0
			}
		}
		return false
	}

	switch {
	case ev.Key() == tcell.KeyEsc, ev.Key() == tcell.KeyCtrlC:
		stopPlayback()
		removeCacheDir()
		logInfo("程序退出")
		sendNotification("播放器退出", "Chillhop播放器已退出")
		return true
	case ev.Key() == tcell.KeyEnter:
		if len(filteredTracks) == 0 {
			drawStatus("无曲目可播放")
			return false
		}
		if selectedIdx < 0 || selectedIdx >= len(filteredTracks) {
			selectedIdx = 0
		}
		go func(t Track) {
			if err := playTrack(&t); err != nil {
				logError("播放失败: %v", err)
				drawStatus("播放失败: " + err.Error())
			}
		}(filteredTracks[selectedIdx])
	case ev.Key() == tcell.KeyDown, ev.Rune() == 'j':
		if len(filteredTracks) > 0 {
			selectedIdx = (selectedIdx + 1) % len(filteredTracks)
		}
	case ev.Key() == tcell.KeyUp, ev.Rune() == 'k':
		if len(filteredTracks) > 0 {
			selectedIdx = (selectedIdx - 1 + len(filteredTracks)) % len(filteredTracks)
		}
	case ev.Rune() == 'g' && lastKey == 'g':
		selectedIdx = 0
	case ev.Rune() == 'G':
		if len(filteredTracks) > 0 {
			selectedIdx = len(filteredTracks) - 1
		}
	}

	switch ev.Rune() {
	case '/':
		isSearching = true
	case 'p':
		togglePause()
	case 's':
		skipTrack()
	case '?':
		showHelp = true
		helpScrollOff = 0
	case 'l':
		showLog = true
		logScrollOff = 0
	}

	return false
}

func clamp(v, min, max int) int {
	if v < min {
		return min
	}
	if v > max {
		return max
	}
	return v
}

func drawUI() {
	screen.Clear()
	width, height := screen.Size()

	drawBorder(width, height)

	if showHelp {
		drawHelpModal(width, height)
		screen.Show()
		return
	}
	if showLog {
		drawLogModal(width, height)
		screen.Show()
		return
	}

	infoHeight := 6
	drawInfoArea(0, 0, width, infoHeight)

	listHeight := height - infoHeight - 4
	drawListArea(0, infoHeight, width, listHeight)

	if isSearching {
		drawInputArea(0, height-4, width, 4)
	} else {
		drawHintArea(0, height-4, width, 4)
	}

	screen.Show()
}

func drawBorder(width, height int) {
	borderColor := getColor(opts.BorderColor)
	style := tcell.StyleDefault.Foreground(borderColor)

	for x := 0; x < width; x++ {
		screen.SetContent(x, 0, tcell.RuneHLine, nil, style)
		screen.SetContent(x, height-1, tcell.RuneHLine, nil, style)
	}
	for y := 0; y < height; y++ {
		screen.SetContent(0, y, tcell.RuneVLine, nil, style)
		screen.SetContent(width-1, y, tcell.RuneVLine, nil, style)
	}

	screen.SetContent(0, 0, tcell.RuneULCorner, nil, style)
	screen.SetContent(width-1, 0, tcell.RuneURCorner, nil, style)
	screen.SetContent(0, height-1, tcell.RuneLLCorner, nil, style)
	screen.SetContent(width-1, height-1, tcell.RuneLRCorner, nil, style)
}

func drawInfoArea(x, y, width, height int) {
	textColor := getColor(opts.TextColor)
	highlightColor := getColor(opts.HighlightColor)
	style := tcell.StyleDefault.Foreground(textColor)
	titleStyle := style.Bold(true)

	drawText(x+2, y+1, "Chillhop 播放器", titleStyle)

	statusY := y + 3
	if currentTrack != nil {
		nameStyle := tcell.StyleDefault.Foreground(highlightColor).Bold(true)
		drawText(x+2, statusY, "当前: "+currentTrack.Name, nameStyle)

		statusText := "播放中"
		if isPaused {
			statusText = "已暂停"
		}
		drawText(x+2, statusY+1, statusText, style)
	} else {
		drawText(x+2, statusY, "未播放曲目", style)
		drawText(x+2, statusY+1, "按/激活搜索，j/k选择，回车播放", style)
	}
}

func drawListArea(x, y, width, height int) {
	textColor := getColor(opts.TextColor)
	highlightColor := getColor(opts.HighlightColor)
	style := tcell.StyleDefault.Foreground(textColor)
	titleStyle := style.Bold(true)

	drawText(x+2, y+1, fmt.Sprintf("曲目列表 (%d首)", len(filteredTracks)), titleStyle)

	visibleLines := height - 2
	if visibleLines <= 0 {
		return
	}

	start := 0
	if len(filteredTracks) > visibleLines {
		start = selectedIdx - (visibleLines / 2)
		if start < 0 {
			start = 0
		}
		if start+visibleLines > len(filteredTracks) {
			start = len(filteredTracks) - visibleLines
		}
	}

	for i := 0; i < visibleLines && start+i < len(filteredTracks); i++ {
		track := &filteredTracks[start+i]
		lineY := y + 2 + i
		if lineY >= y+height-1 {
			break
		}

		var itemStyle tcell.Style
		if start+i == selectedIdx {
			itemStyle = style.Background(highlightColor).Foreground(tcell.ColorBlack).Bold(true)
			drawText(x+1, lineY, ">", itemStyle)
		} else {
			itemStyle = style
		}

		name := track.Name
		if len(name) > width-6 {
			name = name[:width-9] + "..."
		}
		drawText(x+3, lineY, name, itemStyle)
	}
}

func drawInputArea(x, y, width, height int) {
	textColor := getColor(opts.TextColor)
	highlightColor := getColor(opts.HighlightColor)
	style := tcell.StyleDefault.Foreground(textColor)
	promptStyle := style.Foreground(highlightColor)

	drawText(x+2, y+1, "搜索: ", promptStyle)
	inputX := x + 2 + len("搜索: ")
	drawText(inputX, y+1, query, style)

	if len(query) < width-10 {
		screen.SetContent(inputX+len(query), y+1, []rune(opts.Cursor)[0], nil, style)
	}
}

func drawHintArea(x, y, width, height int) {
	textColor := getColor(opts.TextColor)
	style := tcell.StyleDefault.Foreground(textColor).Foreground(tcell.ColorGray)

	hint := "按/搜索 | ?帮助 | l日志 | p暂停 | s跳过 | Esc退出"
	drawText(x+2, y+1, hint, style)
}

func getHelpContent() []string {
	configPath, _ := getConfigPath()
	return []string{
		"=== Chillhop Player 帮助 ===",
		"",
		"快捷键:",
		"  /           激活搜索框",
		"  ?           显示本帮助（j/k滚动，gg顶部，G底部）",
		"  l           查看日志（j/k滚动，G底部）",
		"  p           暂停/继续播放",
		"  s           跳过当前曲目",
		"  j/k         上下选择(Vim风格)",
		"  gg          跳转到列表顶部(Vim风格)",
		"  G           跳转到列表底部(Vim风格)",
		"  上下键      选择曲目",
		"  回车键      播放选中曲目",
		"  Esc/Ctrl+C  退出程序（自动清理缓存）",
		"",
		"配置项:",
		fmt.Sprintf("  配置文件: %s", configPath),
		"  auto_play: 启动时自动播放（true/false）",
		"  clean_cache_after_play: 播放后清理缓存（true/false）",
		"  auto_play_next: 播放完自动续播下一首（true/false）",
		"",
		"按任意键关闭...",
	}
}

func drawHelpModal(width, height int) {
	boxW := width * 4 / 5
	boxH := height * 4 / 5
	boxX := (width - boxW) / 2
	boxY := (height - boxH) / 2

	borderColor := getColor(opts.BorderColor)
	fillStyle := tcell.StyleDefault.Background(tcell.ColorBlack).Foreground(getColor(opts.TextColor))

	drawBorderInRect(boxX, boxY, boxW, boxH, borderColor)
	fillRect(boxX+1, boxY+1, boxW-2, boxH-2, fillStyle)

	content := getHelpContent()
	maxScroll := max(0, len(content)-(boxH-4))
	helpScrollOff = clamp(helpScrollOff, 0, maxScroll)

	y := boxY + 2
	for i := helpScrollOff; i < len(content) && y < boxY+boxH-2; i++ {
		drawText(boxX+4, y, content[i], fillStyle)
		y++
	}
}

func getLogContent() []string {
	logs := readRecentLogs()
	return append([]string{"=== 最近日志 ===", ""}, append(logs, "", "按任意键关闭...")...)
}

func drawLogModal(width, height int) {
	boxW := width * 4 / 5
	boxH := height * 4 / 5
	boxX := (width - boxW) / 2
	boxY := (height - boxH) / 2

	borderColor := getColor(opts.BorderColor)
	fillStyle := tcell.StyleDefault.Background(tcell.ColorBlack).Foreground(getColor(opts.TextColor))

	drawBorderInRect(boxX, boxY, boxW, boxH, borderColor)
	fillRect(boxX+1, boxY+1, boxW-2, boxH-2, fillStyle)

	content := getLogContent()
	maxScroll := max(0, len(content)-(boxH-4))
	logScrollOff = clamp(logScrollOff, 0, maxScroll)

	y := boxY + 2
	for i := logScrollOff; i < len(content) && y < boxY+boxH-2; i++ {
		line := content[i]
		if len(line) > boxW-8 {
			line = line[:boxW-11] + "..."
		}
		drawText(boxX+4, y, line, fillStyle)
		y++
	}
}

func drawBorderInRect(x, y, w, h int, color tcell.Color) {
	style := tcell.StyleDefault.Foreground(color)
	for i := 0; i < w; i++ {
		screen.SetContent(x+i, y, tcell.RuneHLine, nil, style)
		screen.SetContent(x+i, y+h-1, tcell.RuneHLine, nil, style)
	}
	for i := 0; i < h; i++ {
		screen.SetContent(x, y+i, tcell.RuneVLine, nil, style)
		screen.SetContent(x+w-1, y+i, tcell.RuneVLine, nil, style)
	}
	screen.SetContent(x, y, tcell.RuneULCorner, nil, style)
	screen.SetContent(x+w-1, y, tcell.RuneURCorner, nil, style)
	screen.SetContent(x, y+h-1, tcell.RuneLLCorner, nil, style)
	screen.SetContent(x+w-1, y+h-1, tcell.RuneLRCorner, nil, style)
}

func fillRect(x, y, w, h int, style tcell.Style) {
	for i := 0; i < w; i++ {
		for j := 0; j < h; j++ {
			screen.SetContent(x+i, y+j, ' ', nil, style)
		}
	}
}

func drawText(x, y int, text string, style tcell.Style) {
	for i, r := range text {
		screen.SetContent(x+i, y, r, nil, style)
	}
}

func drawStatusJustMsg(msg string) {
	width, height := screen.Size()
	textColor := getColor(opts.TextColor)
	style := tcell.StyleDefault.Foreground(textColor)
	x := (width - len(msg)) / 2
	y := height - 5
	drawText(x, y, msg, style)
	screen.Show()
}

func drawStatus(msg string) {
	width, height := screen.Size()
	textColor := getColor(opts.TextColor)
	style := tcell.StyleDefault.Foreground(textColor)
	x := (width - len(msg)) / 2
	y := height - 10
	drawText(x, y, msg, style)
	screen.Show()
}

func getColor(colorStr string) tcell.Color {
	if strings.HasPrefix(colorStr, "#") {
		hex := colorStr[1:]
		if len(hex) == 6 {
			val, err := strconv.ParseInt(hex, 16, 32)
			if err == nil {
				return tcell.NewHexColor(int32(val))
			}
		}
	}
	color := tcell.GetColor(colorStr)
	if color == tcell.ColorNone {
		return tcell.ColorWhite
	}
	return color
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}
